package io.github.weiranyi.secskill.controller;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.concurrent.TimeUnit;

/*
    1.一个jetty容器的情况下，可以通过synchronized解决高并发问题
        - synchronized锁的是一个进程下的线程并发，如果分布式环境就是多个进程并发，所以分布式下这种方案失效
    2.解决方案redis的分布式锁
        - setnx命令实现：
            -- k存在，说明有锁，线程阻塞
            -- k不存在，执行完后删除锁
            -- 伪代码：
                //（1）加锁
                boolean f = setnx(lock zhangsan) //返回真表示加锁成功
                if(!f){return 有锁，请等待}
                // （2）中间代码
                // （3）delete(lock) //释放锁
                -- 问题1：以上伪代码会出现锁没有被释放的问题
                    --- 解决：设置key的过期时间
                -- 问题2：针对问题1的解决方案，高并发下，某些因素导致服务器压力过大，每个线程执行的时间不同
                    --- 问题模拟：
                        ---- 线程1：执行需要12s，执行到10s，redis自动过期了key
                        ---- 线程2：执行需要7s，执行到2s时，锁被线程1的主动释放掉了
                    --- 解决：
                        ---- 给每个线程加上UUID，释放的时候判断下是不是自己的锁
                -- 问题3：过期时间咋设定呢？
                    --- 可以开启定时器的线程，当过期时间小于总过期时间1/3时，增加总过期时长（续命）
                -- 问题4：有没有相关框架
                    --- 使用Redisson框架实现分布式锁,关注代码中的变化

 */
@Controller
public class TestKill {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private Redisson redisson;

    @RequestMapping("/kill")
    @ResponseBody
    public synchronized String kill() {
        // 变化1-定义商品id
        String productKey = "XIAOMI11";
        // 变化2-获取锁
        RLock rlock = redisson.getLock(productKey);// 底层继承了setnx，过期时间等操作
        // 变化3-上锁(过期时间为30s)
        rlock.lock(30, TimeUnit.SECONDS);

        try {
            // 1.从redis中获取 手机的库存数量
            int phoneCount = Integer.parseInt(stringRedisTemplate.opsForValue().get("phone"));
            // 2.判断手机的数量是否够秒杀的
            if (phoneCount > 0) {
                phoneCount--;
                // 库存减少后，再将库存的值保存回redis
                stringRedisTemplate.opsForValue().set("phone", phoneCount + "");
                System.out.println("库存剩余：" + phoneCount);
            } else {
                System.out.println("库存不足！");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 变化4-释放锁
            rlock.unlock();
        }
        return "over!";
    }
    @Bean
    public Redisson redisson(){
        Config config = new Config();
        // 使用单个redis服务器
        config.useSingleServer().setAddress("redis://192.168.182.129:6379").setDatabase (0);
        // 使用集群redis：config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://192.168.182.129:6379","redis://192.168.182.129:6380","redis://192.168.182.129:6381");
        return (Redisson)Redisson.create(config);
    }
}

